package com.livestocksolutions;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import com.livestocksolutions.util.UpdateHistory;

/* -- Version History --
 *  1.0 : 22 July 2012 - Initial version.
 *  
 *  1.1 : 22 July 2012 - Changed void setRemovalDate(Date date) to void setRemovalDate(Date date) in case of date < insertionDate
 *  
 *  1.2 : 05 September 2012 - Added private long modifiedTime, private boolean modified and private final long id
 *                       public long getID(), public boolean getModified() and public long getModifiedDate().
 *                       Changed private ArrayList<Cow> cows to private ArrayList<long> cows, private Bull bull to
 *                       private long bull. Changed methods public Cow[] getCows(), public Bull getBull(), addCow(Cow) to match changed
 *                       attributes, modified Constructor to reflect changes.
 */

/**
 * This class creates an association between a Bull and Cow(s) for a particular period
 * @author Adam Parr
 * @version 1.2
 * 
 * @.modified 16 September 2012
 */
public class Join {
	
	/**
	 * Unique ID that identifies this Joining
	 */
	protected final long id;
	
	/**
	 * Keep track of any changes of this Joining.
	 */
	protected HashMap<String,UpdateHistory> update = new HashMap<String,UpdateHistory>();
	/**
	 * The List of cows that were put in with the bull during this period
	 */
	private ArrayList<Long> cows = new ArrayList<Long>();
	/**
	 * The Date the Joining began.
	 */
	private long insertionDate;
	/**
	 * The Date the Joining ended.
	 */
	private long removalDate;
	/**
	 * The Bull that was put in with the cows for this period
	 */
	private long bull;
	
	/**
	 * Create a Joining between the given Bull and the Cow(s) starting at the given date
	 * @param start The Date the period begins from
	 * @param bull The Bull that went in with the cow(s)
	 * @param cows The Cow(s) that went in with the Bull
	 */
	public Join(Date start, Bull bull, Cow[] cows){
		insertionDate=start!=null?start.getTime():new Date().getTime();
		
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getOwnerByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
		
		this.bull=bull.getID();
		bull.addJoining(this);
		for(Cow c : cows){
			addCow(c);
		}
		
		update.put("INSERTDATE", new UpdateHistory(String.valueOf(insertionDate)));
		update.put("BULL", new UpdateHistory(String.valueOf(this.bull)));
	}
	
	/**
	 * Create a Joining between the given Bull and Cow(s) starting at the given date
	 * @param start The Date the period begins from
	 * @param bull The Bull that went in with the cow(s)
	 * @param cow The Cow that went in with the Bull
	 */
	public Join(Date start, Bull bull, Cow cow){
		insertionDate=start!=null?start.getTime():new Date().getTime();
		
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getOwnerByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
		
		this.bull=bull.getID();
		bull.addJoining(this);
		addCow(cow);
		
		update.put("INSERTDATE", new UpdateHistory(String.valueOf(insertionDate)));
		update.put("BULL", new UpdateHistory(String.valueOf(this.bull)));
	}
	
	/**
	 * Create a Joining between the given Bull and Cow(s) starting at the given date
	 * @param start The Date the period begins from
	 * @param bull The Bull that went in with the cow(s)
	 */
	public Join(Date start, Bull bull){
		insertionDate=start!=null?start.getTime():new Date().getTime();
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getOwnerByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
		
		this.bull=bull.getID();
		bull.addJoining(this);
		
		update.put("INSERTDATE", new UpdateHistory(String.valueOf(insertionDate)));
		update.put("BULL", new UpdateHistory(String.valueOf(this.bull)));
	}
	
	/**
	 * Add a cow to this join
	 * @param cow Cow to add to the list
	 */
	public void addCow(Cow cow){
		if(!cows.contains(cow.getID())){
			cows.add(cow.getID());
			UpdateHistory cowList = update.get("COW")!=null?update.get("COW"):new UpdateHistory();
			cowList.setValues(String.valueOf(cow.getID()),new UpdateHistory("true"));
			update.put("COW", cowList);
			cow.addJoining(this);
		}
	}
	
	/**
	 * Get the list of Cows that are in this Join
	 * @return An Array of Cows that participated in this join
	 */
	public Cow[] getCows(){
		int size = cows.size();
		Cow[] ca = new Cow[size];
		for(int x=0; x<size; x++){
			ca[x]=((Cow)LivestockSolutions.getBovineByID(cows.get(x)));
		}
		return ca;
	}
	
	/**
	 * Get the date this Join started
	 * @return The date when the Join began
	 */
	public Date getInsertionDate(){
		return insertionDate==0?null:new Date(insertionDate);
	}
	
	/**
	 * Get the date the Joining ended
	 * @return The date the Joining ended
	 */
	public Date getRemovalDate(){
		return removalDate==0?null:new Date(removalDate);
	}
	
	/**
	 * Get the Bull of this Join
	 * @return The Bull that participated in this Join
	 */
	public Bull getBull(){
		return (Bull)LivestockSolutions.getBovineByID(bull);
	}
	
	/**
	 * Set the Date the Bull was taken away from the Cow(s)
	 * @param date date of removal
	 * @return <code>true</code> if the date is after the insertion date, <code>false</code> otherwise
	 */
	public boolean setRemovalDate(Date date){
		if(date.after(new Date(insertionDate))){
			removalDate = date.getTime();
			update.put("REMOVEDATE", new UpdateHistory(String.valueOf(removalDate)));
			return true;
		}
		return false;
	}
	
	/**
	 * Get the unique id of this Join
	 * @return the id of this Join
	 */
	public long getID(){
		return id;
	}
	
	/**
	 * Updates this Object with the updated values from the different version.
	 * @param changes Joining that represents this Object in a different version.
	 * @return <tt>true</tt> if changes is not null and changes does not equal this object, <tt>false</tt> otherwise.
	 */
	public boolean update(Join changes){
		if(changes!=null && this!=changes){
			if(updateBefore("INSERTDATE",changes)){
				insertionDate=Long.parseLong(changes.update.get("INSERTDATE").getValue());
				update.put("INSERTDATE", new UpdateHistory(String.valueOf(insertionDate)));
			}
			
			if(updateBefore("REMOVEDATE",changes)){
				removalDate=Long.parseLong(changes.update.get("REMOVEDATE").getValue());
				update.put("REMOVEDATE", new UpdateHistory(String.valueOf(removalDate)));
			}
			
			if(updateBefore("BULL",changes)){
				bull=Long.parseLong(changes.update.get("BULL").getValue());
				update.put("BULL", new UpdateHistory(String.valueOf(bull)));
			}
			
			if(changes.update.get("COW")!=null){
				for(String key : changes.update.get("COW").toKeyArray()){
					if(update.get("COW")==null || update.get("COW").getValue(key)==null || !update.get("COW").getValue(key).getDate().after(changes.update.get("COW").getValue(key).getDate())){
						UpdateHistory cowList = update.get("COW")!=null?update.get("COW"):new UpdateHistory();
						if(!changes.update.get("COW").getValue(key).equals("false")){
							// Remove
							cows.remove(Long.parseLong(key));
							cowList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							if(!cows.contains(Long.parseLong(key))){
								cows.add(Long.parseLong(key));
							}
							cowList.setValues(key,new UpdateHistory("true"));
						}
						update.put("COW", cowList);
					}
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Checks to see if the modification date of the item at this position is before the changed value
	 * @param key The String key at which to compare the modification dates of the modified objects.
	 * @param changes The Joining that represents this Joining on another system.
	 * @return <tt>true</tt> if the old value was modified before the changed value. <tt>false</tt> otherwise.
	 */
	protected boolean updateBefore(String key,Join changes){
		if(this.update.get(key)==null && changes.update.get(key)!=null){
			return true;
		}else if(changes.update.get(key)==null){
			return false;
		}
		return !this.update.get(key).getDate().after(changes.update.get(key).getDate());
	}
}
